<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>可点击选中单词的富文本</title>
    <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
    <style>
      /* 基础样式：保持原有富文本布局 */
      .rich-text-container {
        max-width: 800px;
        margin: 20px auto;
        padding: 20px;
        border: 1px solid #eee;
        border-radius: 8px;
      }

      /* 单词span样式：默认状态 + 点击选中状态 */
      .word {
        border-radius: 4px;
        cursor: pointer;
        transition: all 0.2s ease;
        outline: 1px solid red;
      }
      /* 鼠标悬浮提示 */
      .word:hover {
        background-color: #f0f7ff;
        color: #165dff;
      }
      /* 点击选中状态 */
      .word.active {
        background-color: #165dff;
        color: #fff;
      }
      /* 选中时的文本选中效果（避免浏览器默认选中样式冲突） */
      .word::selection {
        background-color: transparent;
      }
      .space{
        margin-right: 3em;
      }

      .checked {
        background-color: #ededed;
      }

      .blank {
        padding: 0 1.5em;
        border-bottom: 1px solid red;
      }

    </style>
  </head>
  <body>
    <!-- 富文本容器：初始加载原始富文本 -->
    <!-- <div class="rich-text-container" id="richTextContainer">
      <p style="background-color: #ccc;">
        felis <br> ___1___ tellus mollis orci, sed rhoncus pronin sapien nunc accuan eget.
				<span>aaaa</span> 
      </p>
			<button>11111</button>
			<button>___2___</button>
      <p>felis tellus mollis orci, sed rhoncus pronin sapien nunc accuan eget.</p>
      <img src="../背景/000000.png" alt="000" />
      <p>felis tellus mollis orci, sed rhoncus pronin sapien nunc accuan eget.</p>
    </div> -->

    <div id="app">
      <div v-html="richText"></div>
      <hr>
      <hr>
      <div class="rich-text" v-html="richText" @click="setColor"></div>
    </div>

  </body>
</html>

<script>
  const { createApp, ref, onMounted } = Vue

  createApp({
    setup() {
      let richText = `
      'aa'.aaa-___0___ bbb's aa-ab  labour practice activities!_x000A_Date:
      <br />
      ___1___, .___2___ bbb1's___3___'aa'
      <span>ccc's</span> aaa 222
      (___4___)
      ` 
        // <hr />
        // Aaa? 'aa'? 'aaa's'?
        // <div class="rich-text-container" id="richTextContainer">
        //   <p>
        //     /“aaa” 'felis'. 
        //     ___1___ "eget".
        //     <p>
        //       11111 2222?
        //       <span>aaa bbb's </span>
        //       </p>
        //     <span>"bbbb'</span>
        //   </p>
        //   <p>
        //     <img src="../背景/000000.png" alt="000" />
        //     <span>00000</span>
        //     aaa
        //     <button>aaa bbb's </button>
        //   </p>
        // </div>
      let defaultWords = ['aaa', 'Bbbb'].map((item ) => item.toLowerCase())

      onMounted(() => {
        let richTextContainer = document.querySelector('.rich-text')
        wrapWordsWithClickableSpan(richTextContainer)
      })

      function wrapWordsWithClickableSpan(richTextContainer) {
        let blankIndex = 0

        // 递归处理所有节点：只操作文本节点，跳过图片等非文本标签
        function processNode(node) {
          // 情况1：当前是文本节点（且内容非空）
          if ( node.nodeType === Node.TEXT_NODE && node.textContent.trim() !== '' ) {
            let text = node.textContent
            text = text.replace(/___\s*(\d+)\s*___/g, '___$1___')

            // 按「空格」拆分单词（保留标点与单词的关联，如 orci,、eget.）
            // 正则说明：匹配「单词+可选标点」的组合，避免拆分后语义断裂
            // const wordRegex = /\b[\w']+[.,!?;:"']?\b/g
            const wordRegex = /(\s+)|(\w+)|([^\s\w]+)/g;
            
            const parts = text.split(wordRegex) // 拆分出「非单词部分（空格/换行）」
            // const words = text.match(wordRegex) || [] // 提取所有「单词+标点」
            let words = text.split(' ')

            console.log( text, '------------------------------------------------------------' );

            // 创建文档片段，避免频繁DOM操作
            const fragment = document.createDocumentFragment()
            let partIndex = 0

            // 交替插入「非单词部分」和「单词span」
            words.forEach((item, index) => {
              let { start, word, end } = getWord(item, defaultWords)
              // console.log( index, '---', item, start, word, end );
              
              // 创建单词span，添加点击选中逻辑
              const wordSpan = document.createElement('span')
              if( (/___\d+___/).test(item) ) {
                // 处理单词填空********************************************************
                let blankList = item.split('___')
                start = blankList[0]
                word = blankList[1]
                end = blankList[2]

                if ( /[A-Za-z]/.test(blankList[0]) ) {
                  start = document.createElement('span')
                  setChecked(start, blankList[0], defaultWords)
                }

                if ( /[A-Za-z]/.test(blankList[2]) ) {
                  end = document.createElement('span')
                  setChecked(end, blankList[2], defaultWords)
                }

                wordSpan.className = `blank blank--${blankIndex}`
                blankIndex++
                wordSpan.textContent = blankIndex
              } else if(  word ) {
                setChecked(wordSpan, word, defaultWords)
              } 

              fragment.appendChild( wordSpan )
              console.log( start.nodeType );
              if( start.nodeType ) {
                fragment.insertBefore(start, wordSpan);
              } else {
                wordSpan.insertAdjacentText('beforebegin', start);
              }
              if( end.nodeType ) {
                wordSpan.insertAdjacentElement('afterend', end);
              } else {
                wordSpan.insertAdjacentText('afterend', end + ' ');
              }
            })

            // 用处理后的片段替换原文本节点
            node.parentNode.replaceChild(fragment, node)
          }

          // 情况2：当前是元素节点（递归处理子节点，跳过图片、br等无需处理的标签）
          else if (node.nodeType === Node.ELEMENT_NODE) {
            // 排除不需要处理的标签（图片、换行等）
            const ignoreTags = ['IMG', 'BR', 'SCRIPT', 'STYLE', 'button']
            if (!ignoreTags.includes(node.tagName)) {
              // 遍历所有子节点（注意：childNodes是动态集合，需转为数组）
              Array.from(node.childNodes).forEach((child) => {
                processNode(child)
              })
            }
          }
        }

        // 开始处理富文本容器的所有节点
        processNode(richTextContainer)
      }


      function setColor(e) {
        console.log(e.target);
        let dom = e.target
        console.log( dom.dataset.word );

        // let doms = document.querySelectorAll('.word')
        // doms?.forEach((item) => {
        //   console.log( item );
        // })
      }

      return {
        wrapWordsWithClickableSpan,
        richText,
        setColor
      }
    }
  }).mount('#app')


  function setChecked (dom, word, defaultWords) {
    dom.className = `word word--${word.toLowerCase()}`
    dom.setAttribute('data-word', word.toLowerCase());
    if ( defaultWords.includes(word.toLowerCase()) ) {
      dom.className = dom.className + ' checked'
    }
    dom.textContent = word
    // return dom
  }


function getWord(item, works = []) {
  let word = ''
  let start = ''
  let end = ''
  let j = item.length - 1
  let i = 0

  while (i < item.length) {
    if (/^[a-zA-Z]$/.test(item[i])) {
      break
    }
    start = start + item[i]
    i += 1
  }

  while (j >= 0) {
    if (/^[a-zA-Z]$/.test(item[j])) {
      break
    }
    end = item[j] + end
    j -= 1
  }

  word = item.slice(i, j + 1)

  // 处理数字
  if (!word && start === end) {
    start = ''
  }

  // console.log( item, ':',   start, '-', word, '-' , end, '--------------------' );

  // // ！！！！！！！！！！！！！！！！！！！！！！  匹配默认单词，拆分dom
  // let word_defalut = works.find((e) => item.includes(e.phrase))
  // if( word_defalut ) {
  //   let arr = item.split(word_defalut.phrase)
  //   start = arr[0] || ''
  //   end = arr[1] || ''
  //   word = word_defalut.value
  // }

  // if( word && word.includes('(') && end.includes(')') ) {
  //   word = word + ')'
  //   end = end.replace(/[)]/g, '');
  //   // ！！！！！！！！！！！！！！！！！！！！！！！！！判断括号里是否有汉字、数字
  //   if( word && /[\u4e00-\u9fa5]|\d/.test(word) ) {
  //     let arr = word.split('(')
  //     word = arr[0]
  //     end = '(' + arr[1]
  //   }
  // }


  // // ！！！！！！！！！！！！！！！！！！！！！！！！！！！！！！！！！！！！！！！！！！！
  // // 从小写的文章中匹配到单词下标，并且将单词用 **** 替换，然后通过下标拿到原文的单词
  // let index = str_lowerCase.indexOf(word)
  // str_lowerCase = str_lowerCase.slice(0, index) + '*'.repeat(word.length) + str_lowerCase.slice(index + word.length, str_lowerCase.length)
  // word = str_default.slice(index, index + word.length)
  // // console.log( word, index, word.length, str_default.slice(index, index + word.length) );

  // if( end ){
  //   let end_index = str_lowerCase.indexOf(end)
  //   str_lowerCase = str_lowerCase.slice(0, end_index) + '*'.repeat(end.length) + str_lowerCase.slice(end_index + end.length, str_lowerCase.length)
  //   end = str_default.slice(end_index, end_index + end.length)
  //   // console.log( end );
  // }

  return {
    start,
    word,
    end
  }
}


</script>